Google Cloud Vision API でPDFからEPUBを作成してみた
西田@大阪@MAD事業部です。
本エントリは クラスメソッド Google Cloud Advent Calendar 2021 の 15日目 の記事です。
今回は Google Cloud Vision API を使って、PDFから文字を抽出し、簡易的なEPUB を作成してみました
Cloud Vision APIとは
Cloud Vision API は機械学習の知識がなくても、簡単に画像の解析が可能でサービスです。画像、PDF/TIFF からテキストを抽出したり(OCR)、ランドマーク検出、顔検出などができます。
参考: 機能リスト | Cloud Vision API | Google Cloud
構成
Input 用の Cloud Storage に PDFファイルをアップロードすると、 Cloud Function が起動し、 Cloud Vision API を利用した文字解析を行い、結果出力用の Cloud Storage に解析結果のJSONを出力します。さらに、出力された JSON を元に EPUB を作っていきます
事前準備
本エントリはCloud SDK がインストールされ、 Cloud Functions、Cloud Build API、Cloud Storage、Cloud Vision API が有効になっている前提で書かれています
参考:
Cloud SDK のインストール | Google Cloud
Console を使用したクイックスタート | Google Cloud Functions に関するドキュメント
Cloud Storage のチュートリアル | Google Cloud Functions に関するドキュメント
始める前に | Cloud Vision API | Google Cloud
Cloud Storage の作成
アップロード用と、結果出力用のバケットを作成します。今回作成したスクリプトではアップロードしたバケットに "-outputs" とサフィックスがついたバケットに結果を出力するようにしています
それぞれのバケットを作成します
例) sample-bucket ⇒ sample-bucket-outputs
gsutil mb gs://${YOUR_INPUT_BUCKET} gsutil mb gs://${YOUR_INPUT_BUCKET}-outputs
コード
こちら にコードをアップロードしてます。 それぞれのファイルを解説していきます
./requirements.txt
使用しているライブラリを列挙してます
google-cloud-vision==2.6.2 google-cloud-storage==1.43.0 ebooklib==0.17.1
./main.py
大きく分けて Cloud Vision API で解析してる処理とEPUBを作成している処理を分けて解説します
Cloud Vison API で解析
全体のソース
def pdf_to_epub(data, context): from google.cloud import vision mime_type = 'application/pdf' batch_size = 2 # get gcs uri from data bucket_name = data['bucket'] output_bucket_name = f'{bucket_name}-outputs' file_name = data['name'] gcs_input_uri = f'gs://{bucket_name}/{file_name}' gcs_output_uri = f'gs://{output_bucket_name}/{file_name}/' client = vision.ImageAnnotatorClient() feature = vision.Feature( type_=vision.Feature.Type.DOCUMENT_TEXT_DETECTION) # Input gcs_source = vision.GcsSource(uri=gcs_input_uri) input_config = vision.InputConfig( gcs_source=gcs_source, mime_type=mime_type) # Output gcs_destination = vision.GcsDestination(uri=gcs_output_uri) output_config = vision.OutputConfig( gcs_destination=gcs_destination, batch_size=batch_size) async_request = vision.AsyncAnnotateFileRequest( features=[feature], input_config=input_config, output_config=output_config ) operation = client.async_batch_annotate_files( requests=[async_request] ) operation.result(timeout=420) create_epub(output_bucket_name, file_name)
今回はPDFを対象に文字解析を行うので DOCUMENT_TEXT_DETECTION を指定しています
feature = vision.Feature( type_=vision.Feature.Type.DOCUMENT_TEXT_DETECTION)
gcs_destination
(出力先のバケット)と batch_size
(一つのJSONファイルに何ページを入れるか)を指定しています
gcs_destination = vision.GcsDestination(uri=gcs_output_uri) output_config = vision.OutputConfig( gcs_destination=gcs_destination, batch_size=batch_size)
Cloud Vision API のPDF/TIFF の解析は files:asyncBatchAnnotate
で非同期に実行する必要があるので、指定しています
async_request = vision.AsyncAnnotateFileRequest( features=[feature], input_config=input_config, output_config=output_config )
今回はそのまま EPUB を作成していくので、 Cloud Vision API の完了を待ちます
operation.result(timeout=420)
EPUB作成
次にEPUB作成部分を見ていきます。 EPUB の作成は使用しているライブラリ aerkalov/ebooklib: Python E-book library for handling books in EPUB2/EPUB3 format - のUsageを参考にしています
def create_epub(bucket, file_name): import json from google.cloud import storage from ebooklib import epub storage_client = storage.Client() prefix = f'{file_name}' output_bucket = storage_client.get_bucket(bucket) blob_list = [blob for blob in list(output_bucket.list_blobs( prefix=prefix )) if not blob.name.endswith('/')] book = epub.EpubBook() book.set_identifier('id123456') book.set_title(file_name) book.set_language('ja') book.add_author('Sample') page_no = 0 first_chapter = None chpters = [] for blob in blob_list: json_string = blob.download_as_string() ouptput = json.loads(json_string) for response in ouptput['responses']: page_no = page_no + 1 annotation = response['fullTextAnnotation'] chapter_file_name = f'chap_{page_no}.xhtml' chapter = epub.EpubHtml(title=f'Chapter{page_no}', file_name=chapter_file_name, lang='ja') chapter.content=u'<p>{}</p>'.format(annotation['text']) book.add_item(chapter) chpters.append(chapter) if not first_chapter: first_chapter = chapter book.toc = (epub.Link('chap_1.xhtml', 'Introduction', 'intro'), (epub.Section('Simple book'), chpters) ) book.spine = ['nav', first_chapter]
Cloud Vision API が出力するJSONの構造はざっくり以下で、このJSONを使ってEPUBを作成していきます
{ "responses": [ // batch_size で指定した数の配列になってる { "fullTextAnnotation": { "pages": {}, // 検出された文字の解析情報 "text": "検出された文字列" }, "context": { "uri": "gs://..." // 解析対象のPDFファイルのURI "pageNumber": 1 // ページ数 } } ] }
出力先のバケットから Cloud Vision API が出力したJSONの一覧を取得してます
output_bucket = storage_client.get_bucket(bucket) blob_list = [blob for blob in list(output_bucket.list_blobs( prefix=prefix )) if not blob.name.endswith('/')]
batch_size
で指定した数だけ output に出力されたJSONの responsesの配列に
なっているので、その分をループしてEPUBのコンテンツを作ってます
for blob in blob_list: json_string = blob.download_as_string() ouptput = json.loads(json_string) for response in ouptput['responses']: page_no = page_no + 1 annotation = response['fullTextAnnotation'] chapter_file_name = f'chap_{page_no}.xhtml' chapter = epub.EpubHtml(title=f'Chapter{page_no}', file_name=chapter_file_name, lang='ja') chapter.content=u'<p>{}</p>'.format(annotation['text']) book.add_item(chapter) chpters.append(chapter) if not first_chapter: first_chapter = chapter
一時的に生成したEPUBファイルを Cloud Functions の一時領域である /tmp
に保存し、それをOutput用の Cloud Storage にアップロードしています。Cloud Functions の /tmp
領域に書き込むと、メモリが消費されるのと、関数インスタンスが再利用された際にファイルが残ってしまうことがあるらしいのでご注意ください
参考:
料金 | Cloud Functions | Google Cloud
Cloud Functionsのファイルシステムについて - みーのぺーじ
epub_file_name = f'{file_name}.epub' epub_tmp_path = f'/tmp/{file_name}-{epub_file_name}' epub.write_epub(epub_tmp_path, book, {}) epub_blob = output_bucket.blob(f'{file_name}/{epub_file_name}') epub_blob.upload_from_filename(epub_tmp_path)
デプロイ
gcloud functions deploy pdf_to_epub \ --runtime python39 \ --trigger-resource ${YOUR_INPUT_BUCKET} \ --trigger-event google.storage.object.finalize \ --timeout=300
それぞれ以下のパラメーターを指定しています
pdf_to_epub
⇒ main.py 内の関数名
--runtime
⇒ Python のバージョン
--trigger-resource
⇒ Cloud Functions を設定するバケット
--trigger-event
⇒ Cloud Functionを発動したいトリガーを設定してます。今回はオブジェクトが作成されたタイミングで発動するように設定しています。イベントの種類については こちら を参考にしてください
--timeout
=> Cloud Vision API の処理結果を待つ関係で、時々 Cloud Functions のデフォルトのタイムアウトである60秒を超えるので多めに設定しています
結果
Cloud Vision API の結果は Output のバケットにJSON形式で保存されています。コード中のbatch_size
で指定したページ毎に1つのファイルのJSONとしてまとまって保存されています
上記で使われていPDF( デジタル・ディバイド解消に向けた技術等研究開発 )を解析して出力結果を見ていきます
1ページ目
新たなICTサービスの研究開発を行う\n民間企業等の皆さんへ\n総務省\n補助金事業募集\nデジタル・ディバイド解消に向けた\n技術等研究開発\n高齢者・障害者向けICTサービスの充実を図る\n研究開発を行う企業等の取組に助成します。\nco\neee\nhttps://www.soumu.go.jp/main_sosiki/joho_tsusin/b_free/b_free03.html\nお問い合わせ先\nリサイクル適性(A\n総務省情報流通行政局情報流通振興課情報活用支援室\n〒100-8926 東京都千代田区霞が関2-1-2 中央合同庁舎第2号館\n# 03-5253-5743 FAX 03-5253-6041\nこの印刷物は、印刷用の紙へ\nリサイクルできます。\n
改行させて整形してみます
新たなICTサービスの研究開発を行う 民間企業等の皆さんへ 総務省 補助金事業募集 デジタル・ディバイド解消に向けた 技術等研究開発 高齢者・障害者向けICTサービスの充実を図る 研究開発を行う企業等の取組に助成します。 co eee https://www.soumu.go.jp/main_sosiki/joho_tsusin/b_free/b_free03.html お問い合わせ先 リサイクル適性(A 総務省情報流通行政局情報流通振興課情報活用支援室 〒100-8926 東京都千代田区霞が関2-1-2 中央合同庁舎第2号館 # 03-5253-5743 FAX 03-5253-6041 この印刷物は、印刷用の紙へ リサイクルできます。
ノイズがあるもののかなりの精度で解析できています
3ページ目
表組が使われていたり、アイコンと重なって文字が書かれていたりする、3枚目の抽出結果を見てみます
かなりの分量なので、一部抜粋して比較してみます
助成事例 研究開発 事業名 マルチメディアDAISYの自動制作・利用システムの 障害者支援研究開発 聴覚障害者向け会議支援システムの研究開発 印刷物から抽出したテキストと肉声音声を同期させ たマルチメディアDAISYを自動制作するとともに、即 時にスマートフォン、タブレット端末での利用が可能 なシステムを実現することで、読字障がい者が必要な 時にいつでもどこでも情報の入手が可能となる。 平成27年1月、マルチメディアDAISY/テキスト DAISY製作ソフトウェア「PLEXTALK Producer」とし て販売開始。 PLEXTALK Producer マルチメディアDAISY製作の流れ ポータルサーチャンスでしが、 になりました。 plet the mentom Tepernatete SIE それは、 wrajore チー:3時間からです。 メンテナンスはさような aree Meet ant marracertener ①テキストを取り込む インポート エクスポート・ TXT バックアップ! 復元ー man) のナンスでしたが、 HO 11 Then Rese-thropkiyanet
表組部分に関しては問題なくテキストが抽出されています。また、本文中に掲載されているチャットの洗いサンプル画像の文字も洗いながら識別されてそうです。また、アイコンと重なった文字についても抽出できています
4ページ目
最後に縦書きもある4ページ目の結果を確認していきます
縦書き部分を一部抜粋して整形します
公募 事後評価結果報告 提案 採択 交付決定・研究開発開始 実地調査(中間) 実地調査(最終) 経理検査
きれいに見た目通りの順番(左から右)ではないですが、縦書きでもテキストの抽出はされてそうです。
EPUB
出力されたEPUBも見てみます
元のPDFのページを分けれたものの、ページとして連続してページ送りできないなどなど課題は残るものの、EPUBとして開くことができました。今後EPUBの仕様を勉強して改善していきます
最後に少しだけハマったことをメモ程度に
以下のメッセージが出てエラーになりました
You are attempting to perform an operation that requires a project id, with none configured. Please re-run gsutil config and make sure to follow the instructions for finding and entering your default project id.
以下のコマンドでデフォルトのプロジェクトを設定するといけました
gcloud config set project ${YOUR_PROJECT_ID}
Cloud Vision API APIを有効化してない時に以下のメッセージが出ました
403 Cloud Vision API has not been used in project ${YOUR_PROJECT} before or it is disabled. Enable it by visiting
参考
ファイル内のテキストを検出する(PDF / TIFF) | Cloud Vision API | Google Cloud